﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

using Nova.CodeDOM;

namespace Nova.UI
{
    /// <summary>
    /// The view model for a <see cref="CodeDOM.MethodRef"/>.
    /// </summary>
    public class MethodRefVM : TypeRefBaseVM
    {
        #region /* STATICS */

        internal static void AddViewModelMapping()
        {
            CreateViewModel.Add(typeof(MethodRef),
                delegate(CodeObject codeObject, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary) { return new MethodRefVM((MethodRef)codeObject, dictionary); });
        }

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a view model instance for the specified <see cref="CodeDOM.MethodRef"/>.
        /// </summary>
        public MethodRefVM(MethodRef methodRef, Dictionary<CodeObject, CodeObjectVM> dictionary)
            : base(methodRef, true, dictionary)
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The underlying <see cref="CodeDOM.MethodRef"/> model.
        /// </summary>
        public MethodRef MethodRef
        {
            get { return (MethodRef)CodeObject; }
        }

        #endregion

        #region /* METHODS */

        #endregion

        #region /* RENDERING */

        public override void RenderExpression(CodeRenderer renderer, RenderFlags flags)
        {
            MethodRef methodRef = MethodRef;

            // If we have no dot prefix, and the ShowParentTypes flag is set, then render all parent types
            // (this shouldn't occur in display of code, but only when displaying an evaluated type reference,
            // such as in a tooltip).  Generic methods won't include type arguments for enclosing types, so
            // we don't have to worry about them.
            if (!flags.HasFlag(RenderFlags.HasDotPrefix) && flags.HasFlag(RenderFlags.ShowParentTypes))
            {
                TypeRefBase typeRef = methodRef.GetDeclaringType();
                if (typeRef != null)
                {
                    CreateVM(typeRef).Render(renderer, flags);
                    renderer.RenderText(Dot.ParseToken, PUNC_BRUSH, this);
                    flags |= RenderFlags.HasDotPrefix;
                }
            }

            object reference = methodRef.Reference;
            if (reference is MethodDeclBase)
                renderer.RenderText(((MethodDeclBase)reference).Name, IDENTIFIER_BRUSH, this);
            else if (reference is MethodInfo)
                renderer.RenderText(((MethodInfo)reference).Name, IDENTIFIER_BRUSH, this);

            if (!methodRef.HasInferredTypeArguments || flags.HasFlag(RenderFlags.Description))
                RenderTypeArguments(renderer, _typeArgumentVMs, flags, this);
        }

        public static void RenderMethodInfo(CodeRenderer renderer, MethodInfo methodInfo, RenderFlags flags, CodeObjectVM tag)
        {
            RenderFlags passFlags = flags & ~RenderFlags.Description;
            bool hasBorder = flags.HasFlag(RenderFlags.ForceBorder);
            if (hasBorder)
                renderer.CreateBorder(MethodDeclBaseVM.StaticBorderBrush, MethodDeclBaseVM.StaticBackgroundBrush, tag);

            if (!flags.HasFlag(RenderFlags.NoPreAnnotations))
            {
                AttributeVM.RenderAttributes(renderer, methodInfo, tag);
                AttributeVM.RenderAttributes(renderer, methodInfo.ReturnParameter, tag, AttributeTarget.Return);
            }
            Modifiers modifiers = MethodRef.GetMethodModifiers(methodInfo);
            renderer.RenderText(ModifiersHelpers.AsString(modifiers), KEYWORD_BRUSH, tag);
            bool isConversionOperator = (modifiers.HasFlag(Modifiers.Explicit) || modifiers.HasFlag(Modifiers.Implicit));
            if (!isConversionOperator)
            {
                Type returnType = methodInfo.ReturnType;
                RenderTypeAsTypeRefVM(renderer, returnType, passFlags, tag);  // Render as TypeRefVM for nested tooltips
                renderer.RenderText(" ", PUNC_BRUSH, tag);
            }
            Type declaringType = methodInfo.DeclaringType;
            RenderTypeAsTypeRefVM(renderer, declaringType, passFlags, tag);  // Render as TypeRefVM for nested tooltips
            DotVM.RenderDot(renderer, tag);

            if (methodInfo.IsGenericMethod)
                RenderGenericMember(renderer, methodInfo.Name, methodInfo.GetGenericArguments(), passFlags, IDENTIFIER_BRUSH, tag);
            else if (methodInfo.Name.StartsWith(Operator.NamePrefix))
            {
                // Convert the internal name into the appropriate symbol
                renderer.RenderText(OperatorDecl.ParseToken, KEYWORD_BRUSH, tag);
                renderer.RenderText(" ", PUNC_BRUSH, tag);
                if (isConversionOperator)
                    RenderTypeAsTypeRefVM(renderer, methodInfo.ReturnType, passFlags, tag);  // Render as TypeRefVM for nested tooltips
                else
                    renderer.RenderText(OperatorDecl.GetOperatorSymbol(methodInfo.Name), IDENTIFIER_BRUSH, tag);
            }
            else
                renderer.RenderText(methodInfo.Name, IDENTIFIER_BRUSH, tag);

            RenderMethodParameters(renderer, methodInfo, passFlags, tag);

            // Render type constraints (if any)
            if (methodInfo.IsGenericMethod)
                RenderConstraints(renderer, methodInfo.GetGenericMethodDefinition().GetGenericArguments(), passFlags, tag);

            // If we have a border, return to the previous one
            if (hasBorder)
                renderer.ReturnToBorderParent(tag);
        }

        public static void RenderMethodParameters(CodeRenderer renderer, MethodBase methodBase, RenderFlags flags, CodeObjectVM tag)
        {
            renderer.RenderText(MethodDeclBase.ParseTokenStart, PUNC_BRUSH, tag);
            ICollection parameters = MethodRef.GetMethodParameters(methodBase);
            if (parameters is ParameterInfo[])
                RenderParameters(renderer, (ParameterInfo[])parameters, flags, tag);
            else
            {
                ChildListVM<ParameterDeclVM> parameterVMs = CreateListVM<ParameterDecl, ParameterDeclVM>((ChildList<ParameterDecl>)parameters, tag);
                renderer.RenderList(parameterVMs, flags, null);
            }
            renderer.RenderText(MethodDeclBase.ParseTokenEnd, PUNC_BRUSH, tag);
        }

        public static void RenderParameters(CodeRenderer renderer, ParameterInfo[] parameters, RenderFlags flags, CodeObjectVM tag)
        {
            for (int i = 0; i < parameters.Length; ++i)
            {
                ParameterInfo parameterInfo = parameters[i];
                if (i > 0)
                    renderer.RenderText(ParameterDecl.ParseTokenSeparator + " ", PUNC_BRUSH, tag);

                bool hasBorder = !flags.HasFlag(RenderFlags.NoBorder);
                if (hasBorder)
                    renderer.CreateBorder(VariableDeclVM.StaticBorderBrush, VariableDeclVM.StaticBackgroundBrush, tag);

                ParameterRefVM.RenderParameterInfo(renderer, parameterInfo, flags, tag);

                // If we have a border, return to the previous one);
                if (hasBorder)
                    renderer.ReturnToBorderParent(tag);
            }
        }

        #endregion
    }
}
